
const {
	Logger, logger, DebugSession,
	LoggingDebugSession,
	InitializedEvent, TerminatedEvent, StoppedEvent, BreakpointEvent, OutputEvent,
	ProgressStartEvent, ProgressUpdateEvent, ProgressEndEvent, InvalidatedEvent,
	Thread, StackFrame, Scope, Source, Handles, Breakpoint, MemoryEvent
} = require('@vscode/debugadapter');
const vscode = require("vscode");

const { basename } = require('path-browserify');
const { MockRuntime, RuntimeVariable, timeout } = require('./tecogdbRuntime')
const { Subject } = require('await-notify');
const base64 = require('base64-js');
const { Utils } = require('../utils');
let myfunction = new Utils;

class TecoDebugSession extends LoggingDebugSession {
	static threadID = 1;
	_variableHandles = new Handles();
	_configurationDone = new Subject();
	_cancellationTokens = new Map();
	_reportProgress = false;
	_progressId = 10000;
	_cancelledProgressId = undefined;
	_isProgressCancellable = true;
	_valuesInHex = false;
	_useInvalidatedEvent = false;
	_addressesInHex = true;

	constructor(fileAccessor) {
		super("mock-debug.txt");
		this.flag = false;
		this.isFirst = false;
		this.f11Flag = false;
		this.consoleText = '';
		this.workspaceFileAccessor = fileAccessor;
		this.setDebuggerLinesStartAt1(false);
		this.setDebuggerColumnsStartAt1(false);
		this._runtime = new MockRuntime(fileAccessor, this);

		// setup event handlers
		this._runtime.on('stopOnEntry', (e) => {
			let id = e == undefined ? 1 : e
			this.sendEvent(new StoppedEvent('entry', parseInt(id)));
		});
		this._runtime.on('stopOnStep', (e) => {
			let id = e == undefined ? 1 : e
			this.sendEvent(new StoppedEvent('step', parseInt(id)));
		});
		this._runtime.on('stopOnBreakpoint', (e) => {
			let id = e == undefined ? 1 : e
			this.sendEvent(new StoppedEvent('breakpoint', parseInt(id)));
		});
		this._runtime.on('stopOnDataBreakpoint', (e) => {
			let id = e == undefined ? 1 : e
			this.sendEvent(new StoppedEvent('data breakpoint', parseInt(id)));
		});
		this._runtime.on('stopOnInstructionBreakpoint', (e) => {
			let id = e == undefined ? 1 : e
			this.sendEvent(new StoppedEvent('instruction breakpoint', parseInt(id)));
		});
		this._runtime.on('stopOnException', (exception) => {
			if (exception) {
				this.sendEvent(new StoppedEvent(`exception(${exception})`, TecoDebugSession.threadID));
			} else {
				this.sendEvent(new StoppedEvent('exception', TecoDebugSession.threadID));
			}
		});
		this._runtime.on('breakpointValidated', (bp) => {
			let argg = { verified: bp.verified, id: bp.id, line: bp.line };
			this.sendEvent(new BreakpointEvent('change', argg));
		});

		this._runtime.on('output', (type, text, filePath, line, column) => {

			let category;
			switch (type) {
				case 'prio': category = 'important'; break;
				case 'out': category = 'stdout'; break;
				case 'err': category = 'stderr'; break;
				default: category = 'console'; break;
			}
			const e = new OutputEvent(`${text}\n`, category);

			if (text === 'start' || text === 'startCollapsed' || text === 'end') {
				e.body.group = text;
				e.body.output = `group-${text}\n`;
			}

			e.body.source = this.createSource(filePath);
			e.body.line = this.convertDebuggerLineToClient(line);
			e.body.column = this.convertDebuggerColumnToClient(column);
			this.sendEvent(e);
		});
		this._runtime.on('end', () => {
			this.sendEvent(new TerminatedEvent());
		});
	}

	initializeRequest(response, args) {
		super.initializeRequest(response, args);
		if (args.supportsProgressReporting) {
			this._reportProgress = true;
		}
		if (args.supportsInvalidatedEvent) {
			this._useInvalidatedEvent = true;
		}

		// build and return the capabilities of this debug adapter:
		response.body = response.body || {};

		// the adapter implements the configurationDone request.
		response.body.supportsConfigurationDoneRequest = true;

		// make VS Code use 'evaluate' when hovering over source
		response.body.supportsEvaluateForHovers = true;

		// make VS Code show a 'step back' button
		//  response.body.supportsStepBack = true;

		// make VS Code support data breakpoints
		response.body.supportsDataBreakpoints = true;

		// make VS Code support completion in REPL
		response.body.supportsCompletionsRequest = true;
		response.body.completionTriggerCharacters = [".", "["];

		// make VS Code send cancel request
		response.body.supportsCancelRequest = true;

		// make VS Code send the breakpointLocations request
		response.body.supportsBreakpointLocationsRequest = true;

		// make VS Code provide "Step in Target" functionality
		response.body.supportsStepInTargetsRequest = true;

		// the adapter defines two exceptions filters, one with support for conditions.
		response.body.supportsExceptionFilterOptions = true;
		response.body.exceptionBreakpointFilters = [
			{
				filter: 'namedException',
				label: "Named Exception",
				description: `Break on named exceptions. Enter the exception's name as the Condition.`,
				default: false,
				supportsCondition: true,
				conditionDescription: `Enter the exception's name`
			},
			{
				filter: 'otherExceptions',
				label: "Other Exceptions",
				description: 'This is another exception',
				default: true,
				supportsCondition: false
			}
		];

		// make VS Code send exceptionInfo request
		response.body.supportsExceptionInfoRequest = true;

		// make VS Code send setVariable request
		response.body.supportsSetVariable = true;

		// make VS Code send setExpression request
		response.body.supportsSetExpression = true;

		// make VS Code send disassemble request
		response.body.supportsDisassembleRequest = true;
		response.body.supportsSteppingGranularity = true;
		response.body.supportsInstructionBreakpoints = true;

		// make VS Code able to read and write variable memory
		response.body.supportsReadMemoryRequest = true;
		response.body.supportsWriteMemoryRequest = true;

		response.body.supportSuspendDebuggee = true;
		response.body.supportTerminateDebuggee = true;
		response.body.supportsFunctionBreakpoints = true;
		response.body.supportsDelayedStackTraceLoading = true;

		this.sendResponse(response);

		// since this debug adapter can accept configuration requests like 'setBreakpoint' at any time,
		// we request them early by sending an 'initializeRequest' to the frontend.
		// The frontend will end the configuration sequence by calling 'configurationDone' request.
		this.sendEvent(new InitializedEvent());
	};

	configurationDoneRequest(response, args) {
		super.configurationDoneRequest(response, args);

		// notify the launchRequest that configuration has finished
		this._configurationDone.notify();
	}

	async disconnectRequest(response, args, request) {
		await this._runtime.getGdb().send('quit\n');
		console.log(`disconnectRequest suspend: ${args.suspendDebuggee}, terminate: ${args.terminateDebuggee}`);
	}

	async attachRequest(response, args) {
		return this.launchRequest(response, args);
	}

	async launchRequest(response, args) {
		// 判断工作目录下是否存在.tcdmp文件
		const uriArray = await myfunction.findTCDMPFiles('**/*.tcdmp');
		// 将URI转换为快捷方式列表项
		const items = uriArray.map(uri => ({ label: uri.fsPath }));
		var selectFile;
		if (items.length != 0) {
			const newElement = { label: '在线调试' };
			items.unshift(newElement);

			const selectedItem = await vscode.window.showQuickPick(items, {
				ignoreFocusOut: true,
				placeHolder: '请选择文件'
			});
			if (selectedItem.length != 0) {
				selectFile = selectedItem.label;
				if (selectFile == "在线调试") {
					this.flag = true;  // 在线
					this._runtime.sendFlag(this.flag);
				} else {
					this.flag = false;  // 离线
					this._runtime.sendFlag(this.flag);
				}
			} else {
				this.flag = true;
				this._runtime.sendFlag(this.flag);
			}
		} else {
			this.flag = true;
			this._runtime.sendFlag(this.flag);
		}

		if (this.flag) {
			// 在线
			// super.launchRequest(response, args);
			// make sure to 'Stop' the buffered logging if 'trace' is not set
			logger.setup(args.trace ? Logger.LogLevel.Verbose : Logger.LogLevel.Stop, false);

			// wait 1 second until configuration has finished (and configurationDoneRequest has been called)
			await this._configurationDone.wait(1000);

			// start the program in the runtime
			await this._runtime.start(args.program, !!args.stopOnEntry, !args.noDebug);

			// await this._runtime.start("C:\\CPPProject\\multfilesdebug\\build\\multfiledebug", !!args.stopOnEntry, !args.noDebug);
			if (args.compileError) {
				// simulate a compile/build error in "launch" request:
				// the error should not result in a modal dialog since 'showUser' is set to false.
				// A missing 'showUser' should result in a modal dialog.
				this.sendErrorResponse(response, {
					id: 1001,
					format: `compile error: some fake error.`,
					showUser: args.compileError === 'show' ? true : (args.compileError === 'hide' ? false : undefined)
				});
			} else {
				this.sendResponse(response);
			}
		} else {
			// 离线
			console.log(selectFile);
			await this._runtime.getGdb().offlinesend('target tecocore ' + selectFile + '\n');
			vscode.debug.onDidChangeActiveDebugSession(e => {
				if (!this.f11Flag) {
					vscode.commands.executeCommand('workbench.action.debug.stepInto');
					this.f11Flag = true;
				}
			});
			this.sendResponse(response);
		}
	}

	setFunctionBreakPointsRequest(response, args, request) {
		this.sendResponse(response);
	}

	async setBreakPointsRequest(response, args) {
		let path = args.source.path;
		const clientLines = args.lines || [];

		// clear all breakpoints for this file
		await this._runtime.clearBreakPoints(path);

		// set and verify breakpoint locations
		const actualBreakpoints0 = clientLines.map(async l => {
			const { verified, line, id } = await this._runtime.setBreakPoint(path, this.convertClientLineToDebugger(l));
			const bp = new Breakpoint(verified, this.convertDebuggerLineToClient(line));
			bp.setId(id);
			return bp;
		});
		const actualBreakpoints = await Promise.all(actualBreakpoints0);
		if (this._runtime.isRunning()) {
			await this._runtime.create_breakPoints(path);
		}
		// send back the actual breakpoint positions
		response.body = {
			breakpoints: actualBreakpoints
		};
		this.sendResponse(response);
	}

	ConvertClientLineToDebugger(x) {
		return this.convertClientLineToDebugger(x);
	}

	ConvertDebuggerLineToClient(x) {
		return this.convertDebuggerLineToClient(x);
	}

	breakpointLocationsRequest(response, args, request) {

		if (args.source.path) {
			const bps = this._runtime.get_breakPoints(args.source.path, this.convertClientLineToDebugger(args.line));
			response.body = {
				breakpoints: bps.map(col => {
					return {
						line: args.line,
						column: this.convertDebuggerColumnToClient(col)
					};
				})
			};
		} else {
			response.body = {
				breakpoints: []
			};
		}
		this.sendResponse(response);
	}

	async setExceptionBreakPointsRequest(response, args) {

		let namedException = undefined;
		let otherExceptions = false;

		if (args.filterOptions) {
			for (const filterOption of args.filterOptions) {
				switch (filterOption.filterId) {
					case 'namedException':
						namedException = args.filterOptions[0].condition;
						break;
					case 'otherExceptions':
						otherExceptions = true;
						break;
				}
			}
		}

		if (args.filters) {
			if (args.filters.indexOf('otherExceptions') >= 0) {
				otherExceptions = true;
			}
		}

		this._runtime.setExceptionsFilters(namedException, otherExceptions);

		this.sendResponse(response);
	}

	exceptionInfoRequest(response, args) {
		response.body = {
			exceptionId: 'Exception ID',
			description: 'This is a descriptive description of the exception.',
			breakMode: 'always',
			details: {
				message: 'Message contained in the exception.',
				typeName: 'Short type name of the exception object',
				stackTrace: 'stack frame 1\nstack frame 2',
			}
		};
		this.sendResponse(response);
	}

	async threadsRequest(response) {

		// runtime supports no threads so just return a default thread.
		if (this.flag) {
			// this._runtime.gdb.threads;
			// let lthreads = [];
			// for (let i = 0; i < this._runtime.gdb.threads.length; ++i) {
			// 	lthreads.push(new Thread(TecoDebugSession.threadID + i, "[" + this._runtime.gdb.threads[i] + "]"));
			// }
			// response.body = {
			// 	threads: lthreads
			// };

			response.body = {
				threads: [
					new Thread(TecoDebugSession.threadID, "thread 1"),
					new Thread(TecoDebugSession.threadID + 1, "thread 2"),
					new Thread(TecoDebugSession.threadID + 2, "thread 3"),
				]
			};
		} else {
			response.body = {
				threads: [
					new Thread(TecoDebugSession.threadID, "thread 1"),
					new Thread(TecoDebugSession.threadID + 1, "thread 2"),
					new Thread(TecoDebugSession.threadID + 2, "thread 3"),
				]
			};
		}
		this.sendResponse(response);
	}

	async stackTraceRequest(response, args) {

		const startFrame = typeof args.startFrame === 'number' ? args.startFrame : 0;
		const maxLevels = typeof args.levels === 'number' ? args.levels : 1000;
		const endFrame = startFrame + maxLevels;

		const stk = await this._runtime.stack(startFrame, endFrame);
		response.body = {
			stackFrames: stk.frames.map(f => new StackFrame(f.index, f.name, this.createSource(f.file), f.line)),
			totalFrames: stk.count
		};
		this.sendResponse(response);
	}

	async scopesRequest(response, args) {
		if (this.flag) {
			await this._runtime.getGdb().send(`frame ${args.frameId}\n`);
		} else {
			await this._runtime.getGdb().offlinesend(`f ${args.frameId}\n`);
		}
		
		response.body = {
			scopes: [
				new Scope("Locals", this._variableHandles.create('locals'), false),
				new Scope("Globals", this._variableHandles.create('globals'), true)
			]
		};
		this.sendResponse(response);
	}

	async writeMemoryRequest(response, { data, memoryReference, offset = 0 }) {
		const variable = this._variableHandles.get(Number(memoryReference));
		if (typeof variable === 'object') {
			const decoded = base64.toByteArray(data);
			variable.setMemory(decoded, offset);
			response.body = { bytesWritten: decoded.length };
		} else {
			response.body = { bytesWritten: 0 };
		}

		this.sendResponse(response);
		this.sendEvent(new InvalidatedEvent(['variables']));
	}

	async readMemoryRequest(response, { offset = 0, count, memoryReference }) {
		const variable = this._variableHandles.get(Number(memoryReference));
		if (typeof variable === 'object' && variable.memory) {
			const memory = variable.memory.subarray(
				Math.min(offset, variable.memory.length),
				Math.min(offset + count, variable.memory.length),
			);

			response.body = {
				address: offset.toString(),
				data: base64.fromByteArray(memory),
				unreadableBytes: count - memory.length
			};
		} else {
			response.body = {
				address: offset.toString(),
				data: '',
				unreadableBytes: count
			};
		}

		this.sendResponse(response);
	}

	async setVariableRequest(response, args) {
		const container = this._variableHandles.get(args.variablesReference);
		const rv = container === 'locals'
			? this._runtime.getLocalVariable(args.name)
			: container instanceof RuntimeVariable && container.value instanceof Array
				? container.value.find(v => v.name === args.name)
				: undefined;

		if (rv) {
			rv.value = this.convertToRuntime(args.value);
			response.body = await this.convertFromRuntime(rv);

			if (rv.memory && rv.reference) {
				this.sendEvent(new MemoryEvent(String(rv.reference), 0, rv.memory.length));
			}
		}

		this.sendResponse(response);
	}

	continueRequest(response, args) {
		this._runtime.continue(false);
		this.sendResponse(response);
	}

	reverseContinueRequest(response, args) {
		this._runtime.continue(true);
		this.sendResponse(response);
	}

	nextRequest(response, args) {
		this._runtime.step(args.granularity === 'instruction', false);
		this.sendResponse(response);
	}

	stepBackRequest(response, args) {
		this._runtime.step(args.granularity === 'instruction', true);
		this.sendResponse(response);
	}

	stepInTargetsRequest(response, args) {
		const targets = this._runtime.getStepInTargets(args.frameId);
		response.body = {
			targets: targets.map(t => {
				return { id: t.id, label: t.label };
			})
		};
		this.sendResponse(response);
	}

	stepInRequest(response, args) {
		this._runtime.stepIn(args.targetId);
		this.sendResponse(response);
	}

	stepOutRequest(response, args) {
		this._runtime.stepOut();
		this.sendResponse(response);
	}

	async variablesRequest(response, args, request) {

		// let vs = [];

		// const v = this._variableHandles.get(args.variablesReference);
		// if (v === 'locals') {
		// 	vs = this._runtime.getLocalVariables();
		// } else if (v === 'globals') {
		// 	if (request) {
		// 		this._cancellationTokens.set(request.seq, false);
		// 		vs = await this._runtime.getGlobalVariables(() => !!this._cancellationTokens.get(request.seq));
		// 		this._cancellationTokens.delete(request.seq);
		// 	} else {
		// 		vs = await this._runtime.getGlobalVariables();
		// 	}
		// } else if (v && Array.isArray(v.value)) {
		// 	vs = v.value;
		// }

		// response.body = {
		// 	variables: vs.map(v => this.convertFromRuntime(v))
		// };
		// this.sendResponse(response);
		// 在线
		const variables = new Array();
		if (this.flag) {
			const id = this._variableHandles.get(args.variablesReference);

			if (id.startsWith('registers')) {
				let values = await this._runtime.getGdb().getRegisterValues();
				for (var name in values) {
					variables.push({
						name: name,
						value: values[name],
						variablesReference: 0
					});
				}
			}
			if (id.startsWith('locals')) {
				let getvariables = await this._runtime.info();
				if (getvariables == undefined) {
					this.sendResponse(response);
				}
				// console.log(getvariables);
				for (var i = 0; i < getvariables.length; i++) {
					variables.push({
						name: getvariables[i].name,
						value: getvariables[i].value,
						variablesReference: 0
					});
				}
			}
			let mymap = new Map();
			for (let i = 0; i < variables.length; ++i) {
				mymap.set(variables[i].name, variables[i].value);
			}
			this._runtime.variables = mymap;
		} else {
			// 离线
			let getvariables = await this._runtime.info();
			//let getvariables = [{name:'a',value:'101'},{name:'b',value:'101'}];

			for (var i = 0; i < getvariables.length; i++) {
				variables.push({
					name: getvariables[i].name,
					value: getvariables[i].value,
					variablesReference: 0
				});
			}
			if (!this.isFirst) {
				let printText = await this._runtime.getGdb().offlinesend('printExcption\n');
				vscode.debug.activeDebugConsole.appendLine(printText);
				this.isFirst = true;
			}
		}

		response.body = {
			variables: variables
		};
		this.sendResponse(response);
	}

	async evaluateRequest(response, args) {
		let reply;
		let rv;
		switch (args.context) {
			case 'repl':
				// handle some REPL commands:
				// 'evaluate' supports to create and delete breakpoints from the 'repl':
				const matches = /new +([0-9]+)/.exec(args.expression);
				this.consoleText = args.expression;
				if (matches && matches.length === 2) {
					const mbp = await this._runtime.setBreakPoint(this._runtime.sourceFile, this.convertClientLineToDebugger(parseInt(matches[1])));
					const bp = new Breakpoint(mbp.verified, this.convertDebuggerLineToClient(mbp.line), undefined, this.createSource(this._runtime.sourceFile));
					bp.setId(mbp.id);
					this.sendEvent(new BreakpointEvent('new', bp));
					reply = `breakpoint created`;
				} else {
					const matches = /del +([0-9]+)/.exec(args.expression);
					if (matches && matches.length === 2) {
						const mbp = this._runtime.clearBreakPoints(this._runtime.sourceFile);
						if (mbp) {
							const bp = new Breakpoint(false);
							bp.setId(mbp.id);
							this.sendEvent(new BreakpointEvent('removed', bp));
							reply = `breakpoint deleted`;
						}
					} else {
						const matches = /progress/.exec(args.expression);
						if (matches && matches.length === 1) {
							if (this._reportProgress) {
								reply = `progress started`;
								this.progressSequence();
							} else {
								reply = `frontend doesn't support progress (capability 'supportsProgressReporting' not set)`;
							}
						}
					}
				}
				// fall through
				if (this.flag) {
					response.body = {
						result: reply ? reply : `evaluate(context: '${args.context}', '${args.expression}')`,
						variablesReference: 0
					};
					return this.sendResponse(response);
			}
			default:
				this.consoleText = args.expression;
				if (args.expression.startsWith('$')) {
					rv = this._runtime.getLocalVariable(args.expression.substr(1));
				} else {
					rv = new RuntimeVariable('eval', this.convertToRuntime(args.expression));
				}
				break;
		}

		if (rv) {
			const v = await this.convertFromRuntime(rv);
			if (v.value == undefined) {
				return;
			}
			response.body = {
				result: v.value,
				type: v.type,
				variablesReference: v.variablesReference,
				presentationHint: v.presentationHint
			};
		} else {
			response.body = {
				result: reply ? reply : `evaluate(context: '${args.context}', '${args.expression}')`,
				variablesReference: 0
			};
		}
		this.sendResponse(response);
	}

	async setExpressionRequest(response, args) {

		if (args.expression.startsWith('$')) {
			const rv = this._runtime.getLocalVariable(args.expression.substr(1));
			if (rv) {
				rv.value = this.convertToRuntime(args.value);
				response.body = await this.convertFromRuntime(rv);
				this.sendResponse(response);
			} else {
				this.sendErrorResponse(response, {
					id: 1002,
					format: `variable '{lexpr}' not found`,
					variables: { lexpr: args.expression },
					showUser: true
				});
			}
		} else {
			this.sendErrorResponse(response, {
				id: 1003,
				format: `'{lexpr}' not an assignable expression`,
				variables: { lexpr: args.expression },
				showUser: true
			});
		}
	}

	async progressSequence() {

		const ID = '' + this._progressId++;

		await timeout(100);

		const title = this._isProgressCancellable ? 'Cancellable operation' : 'Long running operation';
		const startEvent = new ProgressStartEvent(ID, title);
		startEvent.body.cancellable = this._isProgressCancellable;
		this._isProgressCancellable = !this._isProgressCancellable;
		this.sendEvent(startEvent);
		this.sendEvent(new OutputEvent(`start progress: ${ID}\n`));

		let endMessage = 'progress ended';

		for (let i = 0; i < 100; i++) {
			await timeout(500);
			this.sendEvent(new ProgressUpdateEvent(ID, `progress: ${i}`));
			if (this._cancelledProgressId === ID) {
				endMessage = 'progress cancelled';
				this._cancelledProgressId = undefined;
				this.sendEvent(new OutputEvent(`cancel progress: ${ID}\n`));
				break;
			}
		}
		this.sendEvent(new ProgressEndEvent(ID, endMessage));
		this.sendEvent(new OutputEvent(`end progress: ${ID}\n`));

		this._cancelledProgressId = undefined;
	}

	dataBreakpointInfoRequest(response, args) {

		response.body = {
			dataId: null,
			description: "cannot break on data access",
			accessTypes: undefined,
			canPersist: false
		};

		if (args.variablesReference && args.name) {
			const v = this._variableHandles.get(args.variablesReference);
			if (v === 'globals') {
				response.body.dataId = args.name;
				response.body.description = args.name;
				response.body.accessTypes = ["write"];
				response.body.canPersist = true;
			} else {
				response.body.dataId = args.name;
				response.body.description = args.name;
				response.body.accessTypes = ["read", "write", "readWrite"];
				response.body.canPersist = true;
			}
		}

		this.sendResponse(response);
	}

	setDataBreakpointsRequest(response, args) {

		// clear all data breakpoints
		this._runtime.clearAllData_breakPoints();

		response.body = {
			breakpoints: []
		};

		for (const dbp of args.breakpoints) {
			const ok = this._runtime.setDataBreakpoint(dbp.dataId, dbp.accessType || 'write');
			response.body.breakpoints.push({
				verified: ok
			});
		}

		this.sendResponse(response);
	}

	completionsRequest(response, args) {

		response.body = {
			targets: [
				{
					label: "item 10",
					sortText: "10"
				},
				{
					label: "item 1",
					sortText: "01",
					detail: "detail 1"
				},
				{
					label: "item 2",
					sortText: "02",
					detail: "detail 2"
				},
				{
					label: "array[]",
					selectionStart: 6,
					sortText: "03"
				},
				{
					label: "func(arg)",
					selectionStart: 5,
					selectionLength: 3,
					sortText: "04"
				}
			]
		};
		this.sendResponse(response);
	}

	cancelRequest(response, args) {
		if (args.requestId) {
			this._cancellationTokens.set(args.requestId, true);
		}
		if (args.progressId) {
			this._cancelledProgressId = args.progressId;
		}
	}

	disassembleRequest(response, args) {

		const baseAddress = parseInt(args.memoryReference);
		const offset = args.instructionOffset || 0;
		const count = args.instructionCount;

		const isHex = args.memoryReference.startsWith('0x');
		const pad = isHex ? args.memoryReference.length - 2 : args.memoryReference.length;

		const loc = this.createSource(this._runtime.sourceFile);

		let lastLine = -1;

		const instructions = this._runtime.disassemble(baseAddress + offset, count).map(instruction => {
			const address = instruction.address.toString(isHex ? 16 : 10).padStart(pad, '0');
			const instr = {
				address: isHex ? `0x${address}` : `${address}`,
				instruction: instruction.instruction
			};
			// if instruction's source starts on a new line add the source to instruction
			if (instruction.line !== undefined && lastLine !== instruction.line) {
				lastLine = instruction.line;
				instr.location = loc;
				instr.line = this.convertDebuggerLineToClient(instruction.line);
			}
			return instr;
		});

		response.body = {
			instructions: instructions
		};
		this.sendResponse(response);
	}

	setInstructionBreakpointsRequest(response, args) {

		// clear all instruction breakpoints
		this._runtime.clearInstruction_breakPoints();

		// set instruction breakpoints
		const breakpoints = args.breakpoints.map(ibp => {
			const address = parseInt(ibp.instructionReference);
			const offset = ibp.offset || 0;
			return {
				verified: this._runtime.setInstructionBreakpoint(address + offset)
			};
		});

		response.body = {
			breakpoints: breakpoints
		};
		this.sendResponse(response);
	}

	customRequest(command, response, args) {
		if (command === 'toggleFormatting') {
			this._valuesInHex = !this._valuesInHex;
			if (this._useInvalidatedEvent) {
				this.sendEvent(new InvalidatedEvent(['variables']));
			}
			this.sendResponse(response);
		} else {
			super.customRequest(command, response, args);
		}
	}

	//---- helpers

	convertToRuntime(value) {

		value = value.trim();

		if (value === 'true') {
			return true;
		}
		if (value === 'false') {
			return false;
		}
		if (value[0] === '\'' || value[0] === '"') {
			return value.substr(1, value.length - 2);
		}
		const n = parseFloat(value);
		if (!isNaN(n)) {
			return n;
		}
		return value;
	}

	async convertFromRuntime(v) {
		let res = this._runtime.getLocalVariable(v._value);
		let dapVariable = {
			name: v.name,
			value: v._value,
			type: typeof v.value,
			variablesReference: 0,
			evaluateName: '$' + v.name
		};

		// 	if (v.name.indexOf('lazy') >= 0) {
		// 	// a "lazy" variable needs an additional click to retrieve its value

		// 	dapVariable.value = 'lazy var';		// placeholder value
		// 	v.reference ??= this._variableHandles.create(new RuntimeVariable('', [ new RuntimeVariable('', v.value) ]));
		// 	dapVariable.variablesReference = v.reference;
		// 	dapVariable.presentationHint = { lazy: true };
		// } else {

		// 	if (Array.isArray(v.value)) {
		// 		dapVariable.value = 'Object';
		// 		v.reference ??= this._variableHandles.create(v);
		// 		dapVariable.variablesReference = v.reference;
		// 	} else {

		// 		switch (typeof v.value) {
		// 			case 'number':
		// 				if (Math.round(v.value) === v.value) {
		// 					dapVariable.value = this.formatNumber(v.value);
		// 					(dapVariable).__vscodeVariableMenuContext = 'simple';	// enable context menu contribution
		// 					dapVariable.type = 'integer';
		// 				} else {
		// 					dapVariable.value = v.value.toString();
		// 					dapVariable.type = 'float';
		// 				}
		// 				break;
		// 			case 'string':
		// 				dapVariable.value = `"${v.value}"`;
		// 				break;
		// 			case 'boolean':
		// 				dapVariable.value = v.value ? 'true' : 'false';
		// 				break;
		// 			default:
		// 				dapVariable.value = typeof v.value;
		// 				break;
		// 		}
		// 	}
		// }

		if (v.memory) {
			v.reference ??= this._variableHandles.create(v);
			dapVariable.memoryReference = String(v.reference);
		}
		dapVariable.name = dapVariable.name;
		if (this.flag) {
			dapVariable.value = res;
		} else {
			let text = this.consoleText;
			let result = await this._runtime.getGdb().offlinesend(text + '\n');
			dapVariable.value = result;
		}
		return dapVariable;
	}

	formatAddress(x, pad = 8) {
		return this._addressesInHex ? '0x' + x.toString(16).padStart(8, '0') : x.toString(10);
	}

	formatNumber(x) {
		return this._valuesInHex ? '0x' + x.toString(16) : x.toString(10);
	}

	createSource(filePath) {
		if (filePath == "unknown") {
			return;
		}
		return new Source(basename(filePath), this.convertDebuggerPathToClient(filePath), undefined, undefined, 'mock-adapter-data');
	}

}
module.exports = { TecoDebugSession };
